package com.yuchen.lucene;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;
import org.apache.lucene.document.*;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 6  * Store.COMPRESS:压缩存储,用于长文本或二进制，但性能受损
 * 7  * Store.YES : 用于存储字段值。适用于显示搜索结果的字段。例如，文件路径和URL
 * 8  * Store.NO : 没有存储字段值。例如，电子邮件消息正文. 存储与索引是没有关系的
 * 9  * Index.ANALYZED : 用于字段索引和分析（分词）。例如，电子邮件消息正文和标题
 * 10  * Index.ANALYZED_NO_NORMS : 分词建索引，但是Field的值不像通常那样被保存，而是只取一个byte，这样节约存储空间
 * 11  * Index.NO : 适用于未搜索的字段。例如，仅用于存储字段
 * 12  * Index.NOT_ANALYZED : 用于编制索引但不分析（分词）的字段。它在整体中保留字段的原值。例如，日期和个人名称
 * 13  * Index.NOT_ANALYZED_NO_NORMS : 不分词建索引，Field的值只取一个byte保存，这样节约存储空间
 * 14  * 注意：NORMS存储了boost所需信息，包含了NORM可能会占用更多内存
 * 15  *
 */
public class DocumentHelper {

    private final static Logger LOG = Logger.getLogger(DocumentHelper.class);

    //Searchable 转化为 Document
    public static Document obj2doc(Searchable obj) {
        if (obj == null)
            return null;
        Document doc = new Document();
        doc.add(new LongField(LuceneConstant.FN_ID, obj.id(), Field.Store.YES));
        doc.add(new StringField(LuceneConstant.FN_CLASSNAME, obj.getClass().getName(), Field.Store.YES));

        //存储字段
        List<String> fields = obj.storeFields(); //Store.YES
        if (fields != null && fields.size() > 0) {
            for (String fn : fields) {
                Object val = getReflectionVal(obj, fn);
                if (val != null)
                    doc.add(obj2field(fn, val, true));
            }
        }

        //扩展存储字段
        Map<String, String> eDatas = obj.extendStoreDatas();
        if(eDatas != null) {
            for (String fn : eDatas.keySet()) {
                if (fields != null && fields.contains(fn))
                    continue;
                String fv = eDatas.get(fn);
                if (fv != null)
                    doc.add(obj2field(fn, fv, true));
            }
        }

        //索引字段
        fields = obj.indexFields();
        if (fields != null && fields.size() > 0) {
            for(String fn : fields) {
                String fv = (String) getReflectionVal(obj, fn);
                if(fv != null){
                    TextField tf = new TextField(fn,fv, Field.Store.NO);
                    tf.setBoost(obj.boost());
                    doc.add(tf);
                }
            }
        }

        //扩展索引字段
        eDatas = obj.extendIndexDatas();
        if(eDatas != null) {
            for (String fn : eDatas.keySet()) {
                if (fields != null && fields.contains(fn))
                    continue;
                String fv = eDatas.get(fn);
                if (fv != null) {
                    TextField tf = new TextField(fn, fv, Field.Store.NO);
                    tf.setBoost(obj.boost());
                    doc.add(tf);
                }
            }
        }

        return doc;
    }

    //Document 转化为 Searchable
    public static Searchable doc2obj(Document doc){
        try{
            long id = id(doc);
            if(id <= 0)
                return null;
            Searchable obj = (Searchable)Class.forName(doc.get(LuceneConstant.FN_CLASSNAME)).newInstance();
            obj.setId(id);
            return obj;
        }catch(Exception e){
            LOG.error("Unabled generate object from document#id="+doc.toString(), e);
            return null;
        }
    }

    /**
     * 返回文档中保存的对象 id
     * @param doc
     * @return
     */
    public static long id(Document doc) {
        return NumberUtils.toLong(doc.get(LuceneConstant.FN_ID), 0);
    }

    //通过反射获取字段的值
    private static Object getReflectionVal(Object obj, String field) {
        try {
            return PropertyUtils.getProperty(obj, field);
        } catch (Exception e) {
            LOG.error("反射错误：Unabled to get property '" + field + "' of " + obj.getClass().getName());
            return null;
        }
    }

    /**
     * 创建Field
     *
     * @param fieldName
     * @param value
     * @param store
     * @return
     */
    private static Field obj2field(String fieldName, Object value, boolean store) {
        if (value instanceof Date)
            return new LongField(fieldName, ((Date) value).getTime(), store ? Field.Store.YES : Field.Store.NO);
        if (value instanceof Double)
            return new DoubleField(fieldName, (Double) value, store ? Field.Store.YES : Field.Store.NO);
        if(value instanceof Float)
            return new FloatField(fieldName, (Float) value, store ? Field.Store.YES : Field.Store.NO);
        if(value instanceof Number)
            return new LongField(fieldName, (Long) value, store ? Field.Store.YES : Field.Store.NO);
        //其他默认当字符串处理
        return new StringField(fieldName, (String) value, store ? Field.Store.YES : Field.Store.NO);
    }
}
